#pragma once

#include "icmp_context.hpp"
#include "limbo/details/checksum.h"
#include "limbo/errc.h"
#include "limbo/ip/state.hpp"
#include "limbo/result.h"
#include "opts.h"
#include "types.h"
#include <cstdint>
#include <cstring>

namespace limbo::icmp {

template <typename State> struct IcmpPacket {
  using LowerState = typename State::LowerState;
  using Context = IcmpContext<State>;
  using Container = typename LowerState::Packet;
  static constexpr uint8_t SZ = 4;

  Type type;
  Code code;
  uint16_t checksum;
  Chunk payload;
  Container *container;

  inline static uint32_t prefix(const Context &) { return SZ; }

  static Result send(State &state, const Context &ctx, Chunk buff,
                     const Chunk payload) {
    auto sz = payload.size() + SZ;
    if (buff.size() < sz) {
      auto code = (uint32_t)Errc::no_enough_space;
      return Result::make_error(code, &state);
    }

    auto start = (unsigned char *)buff.data();
    auto ptr = start;

    *ptr++ = (uint8_t)ctx.type;
    *ptr++ = ctx.code;

    auto checksum = ptr;
    *ptr++ = 0;
    *ptr++ = 0;

    std::memcpy(ptr, payload.data(), payload.size());

    /* checksum */
    auto cs = details::checksum(start, sz);
    cs = order::native_to_big((uint16_t)~cs);
    std::memcpy(checksum, &cs, sizeof(cs));

    auto consumed = Chunk(start, sz);
    return Result::make_consumed(&state, consumed);
  }

  Result recv(Chunk buff, State &state, LowerState *ip_state) {
    auto sz = buff.size();
    auto ptr = (unsigned char *)buff.data();
    if (sz < SZ) {
      return Result::make_demand(&state, SZ - sz);
    }
    auto start = ptr;
    (void)start;

    type = (Type)*ptr++;
    code = *ptr++;

    uint16_t checksum;
    memcpy(&checksum, ptr, sizeof(checksum));
    ptr += sizeof(checksum);
    this->checksum = order::big_to_native(checksum);

    payload = Chunk(ptr, sz - SZ);
    container = &ip_state->get_parsed();

    if constexpr (State::opts & Opts::validate_checksum) {
      auto act_cs = (uint16_t) ~(details::checksum(start, sz) - checksum);
      if (act_cs != checksum) {
        auto code = (uint32_t)Errc::icmp_checksum_mismatch;
        return Result::make_error(code, &state, buff);
      }
    }
    /* success */
    return Result::make_consumed(&state, buff);
  }
};

template <typename State>
inline static uint32_t get_prefix(const IcmpContext<State> &) {
  return IcmpPacket<State>::SZ;
}

} // namespace limbo::icmp
